/*
The MIT License

Copyright (c) 2010 Michael Webb

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
//EL
/* Interface : provides methods for handling the creation
 * and lookup of interaces.  This is largely borrowed from the
 * work of the book, Pro JavaScript Design Patterns.
 */
(function() {
	var nid = 0;
	var InterfaceId = function ()
	{
	     return nid++;
	};
	var interfaces = {};
	/**
	 * @namespace
	 */
	CA.Interfaces = {};
	/**
	 * Indicates if Interface Keys should be used.  If used, when
	 * an interface is checked, an attribute named '__interfaces__' 
	 * is looked up on the object.  If it does not exist, an array of
	 * that name is added to the object.  Each interface has a unique
	 * (to the browser) key, and the index of that key will be looked up
	 * in the array.  Failing to find the key will then result in a full
	 * inspection of the methods.  Defaults to true;
	 * @property USE_INTERFACE_KEYS
	 * @type boolean
	 */
	CA.Interfaces["USE_INTERFACE_KEYS"] = true;
	/**
	 * The interface class, derived from the book Pro Javascript Design Patterns 
	 * http://apress.com/book/view/159059908x
	 * This class cannot be instantiated directly, but must be created through the
	 * CA.Interfaces.CreateInterface method.
	 * @memberOf CA.Interfaces
	 * @class
	 * @constructor
	 * @param {string} name The name of the interface
	 * @param {Array} methods An array of method names (strings) that must exist on an object to "implement" this interface
	 */
	var Interface = function(name, methods) {
		if (typeof (interfaces[name]) != "undefined") {
			throw new Error("Interface already exists");
		}
		if (arguments.length != 2) {
			throw new Error("Interface called with " + arguments.length	+ " arguments but expected exactly 2");
		}
		this.name = name;
		this.methods = [];
		for ( var i = 0; i < methods.length; i++) {
			if (typeof (methods[i]) != "string") {
				throw new Error("Methods are expected to be of type string");
			}
			this.methods.push(methods[i]);
		}
		interfaces[name] = this;
		this["__ik__"] = InterfaceId();
	};
	/**
	 *  This method checks to see if the object implements this interface
	 *  @name CA.Interfaces.Interface.prototype.Check
	 *  @function
	 *  @param {Object} o the object to check for having implemented this interface
	 *  @return {boolean} a bool indicating if the object implements this interface
	 */
	Interface.prototype["Check"] = function(o) {
		if (typeof (o) != "object" && o!==null) {
			throw new Error("Object expected.");
		}
		if(typeof o["__interfaces__"] !== "undefined" && o["__interfaces__"].indexOf(this["__ik__"])!=-1){
			return true;
		}
		for ( var i = 0; i < this.methods.length; i++) {
			if (typeof (o[this.methods[i]]) != "function") {
				return false;
			}
		}
		if(CA.Interfaces["USE_INTERFACE_KEYS"]){
			if(typeof o["__interfaces__"] === "undefined"){
				o["__interfaces__"] = [];
			}
			o["__interfaces__"].push(this["__ik__"]);
		}
		return true;
	};

	/**
	 *  This method throws an error if o does not implement this interface
	 *  @name CA.Interfaces.Interface.prototype.Ensure
	 *  @function Ensure
	 *  @param {Object} o the object to check for having implemented this interface
	 */
	Interface.prototype["Ensure"] = function(o) {
		if (!this["Check"](o)) {
			throw new Error("Does not implement interfaces:" + this.name);
		}
	};
	/**
	 * Creates an interface class, derived from the book Pro Javascript Design Patterns 
	 * http://apress.com/book/view/159059908x
	 * @function Create
	 * @param {string} name The name of the interface
	 * @param {array} methods An array of method names (strings) that must exist on an object to "implement" this interface
	 * @return {Object} the interface object
	 */
	CA.Interfaces.Create = function(name, methods) {
		return new Interface(name, methods);
	};
	/**
	 * Creates an interface class, derived from the book Pro Javascript Design Patterns 
	 * http://apress.com/book/view/159059908x
	 * @function Get
	 * @param {string} name The name of the interface
	 * @return {Object} the interface object
	 */
	CA.Interfaces.Get = function(name) {
		return interfaces[name];
	};
	/**
	 *  This method checks to see if the object implements this interface.  NOTE: additional arguments
	 *  following params with the names of the interfaces being checked against o
	 *  @function Check
	 *  @param {Object} o the object to check for having implemented the interface(s)
	 *  @return {boolean} a bool indicating if the object implements the interface(s)
	 */
	CA.Interfaces.Check = function() {
		if (arguments.length < 2) {
			throw new Error("Expected at least two arguments.");
		}
		var instance = arguments[0];
		for ( var i = 1; i < arguments.length; i++) {
			var interfaceName = arguments[i];
			var iface = interfaces[interfaceName];
			if (iface) {
				if (!iface["Check"](instance)) {
					return false;
				}
			} else {
				throw new Error("No such interface defined:" + interfaceName);
			}
		}
		return true;
	};
	/**
	 *  This method throws an error if o does not implement the interface(s)
	 *  one or more arguments following params with the names of the interfaces being checked against o
	 *  @function Ensure
	 *  @param {Object} o the object to check for having implemented the interface(s)
	 */
	CA.Interfaces.Ensure = function() {
		if (arguments.length < 2) {
			throw new Error("Expected at least two arguments.");
		}
		var instance = arguments[0];
		for ( var i = 1; i < arguments.length; i++) {
			var interfaceName = arguments[i];
			var iface = interfaces[interfaceName];
			if (iface) {
				iface["Ensure"](instance);
			} else {
				throw new Error("No such interface defined:" + interfaceName);
			}
		}
		return true;
	};
})();